18. Solution: Write Your Own ViewModel Test

If you want to take a look at the solution up to this point, you can check out this Github repo. You can compare the starter code to where the code is now here.

Step 1: Compare your test to the solution

  1. Compare your solution versus the solution below:

TasksViewModelTest

    @Test
    fun setFilterAllTasks_tasksAddViewVisible() {

        // Given a fresh ViewModel
        val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())

        // When the filter type is ALL_TASKS
        tasksViewModel.setFiltering(TasksFilterType.ALL_TASKS)

        // Then the "Add task" action is visible
        assertThat(tasksViewModel.tasksAddViewVisible.getOrAwaitValue(), `is`(true))
    }

Notice:

  • You create your tasksViewModel using the same AndroidX ApplicationProvider.getApplicationContext() statement
  • You call the setFiltering method, passing in the ALL_TASKS filter type enum
  • You check that the tasksAddViewVisible is true, using the getOrAwaitNextValue method

Step 2: Add a @Before rule

Notice how at the start of both of your tests, you define a TasksViewModel:

TasksViewModelTest

        // Given a fresh ViewModel
        val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())

When you have repeated setup code like this shared between all tests, you can use the @Before annotation to create a setup method and remove repeated code. Since all of these tests are going to test the TasksViewModel and will need a view model, it's safe to move this code to a @Before block.

  1. Create a lateinit instance variable called tasksViewModel
  2. Create a method called setupViewModel
  3. Annotate it with @Before
  4. Move the view model instantiation code to setupViewModel:

TasksViewModelTest

    // Subject under test
    private lateinit var tasksViewModel: TasksViewModel

    @Before
    fun setupViewModel() {
        tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
    }
  1. Run your code!

Warning

Do not do the following, do not initialize the tasksViewModel with its definition:

val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())

This will cause the same instance to be used for all tests. This is something you should avoid because each test should have a fresh instance of the subject under test (the ViewModel in this case).


Your final code for TasksViewModelTest should look like:

TasksViewModelTest

@RunWith(AndroidJUnit4::class)
class TasksViewModelTest {

    // Subject under test
    private lateinit var tasksViewModel: TasksViewModel

    // Executes each task synchronously using Architecture Components.
    @get:Rule
    var instantExecutorRule = InstantTaskExecutorRule()

    @Before
    fun setupViewModel() {
        tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
    }


    @Test
    fun addNewTask_setsNewTaskEvent() {

        // When adding a new task
        tasksViewModel.addNewTask()

        // Then the new task event is triggered
        val value = tasksViewModel.newTaskEvent.awaitNextValue()
        assertThat(
            value?.getContentIfNotHandled(), (not(nullValue()))
        )
    }

    @Test
    fun getTasksAddViewVisible() {

        // When the filter type is ALL_TASKS
        tasksViewModel.setFiltering(TasksFilterType.ALL_TASKS)

        // Then the "Add task" action is visible
        assertThat(tasksViewModel.tasksAddViewVisible.awaitNextValue(), `is`(true))
    }

}